﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading;
using System.Reflection.Emit;

namespace Tezt
{
    public static class DllBuilder<Target>
    {
        public static Type Compose<TSource>()
        {
            Type pointType = null;

            AppDomain currentDom = Thread.GetDomain();

            Console.Write("Please enter a name for your new assembly: ");
            StringBuilder asmFileNameBldr = new StringBuilder();
            asmFileNameBldr.Append(Console.ReadLine());
            asmFileNameBldr.Append(".exe");
            string asmFileName = asmFileNameBldr.ToString();

            AssemblyName myAsmName = new AssemblyName();
            myAsmName.Name = "MyDynamicAssembly";

            AssemblyBuilder myAsmBldr = currentDom.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.RunAndSave);
            ModuleBuilder myModuleBldr = myAsmBldr.DefineDynamicModule(asmFileName, asmFileName);
            TypeBuilder myTypeBldr = myModuleBldr.DefineType("Gronia");
            FieldBuilder xField = myTypeBldr.DefineField("x", typeof(int), FieldAttributes.Private);
            FieldBuilder yField = myTypeBldr.DefineField("y", typeof(int), FieldAttributes.Private);

            Type objType = Type.GetType("System.Object");
            ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);

            Type[] ctorParams = new Type[] { typeof(int), typeof(int) };
            ConstructorBuilder pointCtor = myTypeBldr.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ctorParams);
            ILGenerator ctorIL = pointCtor.GetILGenerator();
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Call, objCtor);
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Ldarg_1);
            ctorIL.Emit(OpCodes.Stfld, xField);
            ctorIL.Emit(OpCodes.Ldarg_0);
            ctorIL.Emit(OpCodes.Ldarg_2);
            ctorIL.Emit(OpCodes.Stfld, yField);
            ctorIL.Emit(OpCodes.Ret);

            Console.WriteLine("Constructor built.");

            MethodBuilder pointDPBldr = myTypeBldr.DefineMethod("DotProduct", MethodAttributes.Public, typeof(int), new Type[] { myTypeBldr });
            
            ILGenerator dpIL = pointDPBldr.GetILGenerator();
            dpIL.Emit(OpCodes.Ldarg_0);
            dpIL.Emit(OpCodes.Ldfld, xField);
            dpIL.Emit(OpCodes.Ldarg_1);
            dpIL.Emit(OpCodes.Ldfld, xField);
            dpIL.Emit(OpCodes.Mul_Ovf_Un);
            dpIL.Emit(OpCodes.Ldarg_0);
            dpIL.Emit(OpCodes.Ldfld, yField);
            dpIL.Emit(OpCodes.Ldarg_1);
            dpIL.Emit(OpCodes.Ldfld, yField);
            dpIL.Emit(OpCodes.Mul_Ovf_Un);
            dpIL.Emit(OpCodes.Add_Ovf_Un);
            dpIL.Emit(OpCodes.Ret);

            // Build the PointMain method.

            Console.WriteLine("DotProduct built.");

            MethodBuilder pointMainBldr = myTypeBldr.DefineMethod("PointMain", MethodAttributes.Public | MethodAttributes.Static, typeof(void), null);
            pointMainBldr.InitLocals = true;
            ILGenerator pmIL = pointMainBldr.GetILGenerator();


            MethodInfo writeMI = typeof(Console).GetMethod("Write", new Type[] { typeof(string) });
            MethodInfo readLineMI = typeof(Console).GetMethod("ReadLine", new Type[0]);
            MethodInfo convertInt32MI = typeof(Convert).GetMethod("ToInt32", new Type[] { typeof(string) });
            Type[] wlParams = new Type[] { typeof(string), typeof(object[]) };
            MethodInfo writeLineMI = typeof(Console).GetMethod("WriteLine", wlParams);

            LocalBuilder x1LB = pmIL.DeclareLocal(typeof(int));
            LocalBuilder y1LB = pmIL.DeclareLocal(typeof(int));
            LocalBuilder x2LB = pmIL.DeclareLocal(typeof(int));
            LocalBuilder y2LB = pmIL.DeclareLocal(typeof(int));
            LocalBuilder point1LB = pmIL.DeclareLocal(myTypeBldr);
            LocalBuilder point2LB = pmIL.DeclareLocal(myTypeBldr);
            LocalBuilder tempObjArrLB = pmIL.DeclareLocal(typeof(object[]));

            pmIL.Emit(OpCodes.Ldstr, "Enter the 'x' value for point 1: ");
            pmIL.EmitCall(OpCodes.Call, writeMI, null);
            pmIL.EmitCall(OpCodes.Call, readLineMI, null);
            pmIL.EmitCall(OpCodes.Call, convertInt32MI, null);
            pmIL.Emit(OpCodes.Stloc, x1LB);

            pmIL.Emit(OpCodes.Ldstr, "Enter the 'y' value for point 1: ");
            pmIL.EmitCall(OpCodes.Call, writeMI, null);
            pmIL.EmitCall(OpCodes.Call, readLineMI, null);
            pmIL.EmitCall(OpCodes.Call, convertInt32MI, null);
            pmIL.Emit(OpCodes.Stloc, y1LB);

            pmIL.Emit(OpCodes.Ldstr, "Enter the 'x' value for point 2: ");
            pmIL.EmitCall(OpCodes.Call, writeMI, null);
            pmIL.EmitCall(OpCodes.Call, readLineMI, null);
            pmIL.EmitCall(OpCodes.Call, convertInt32MI, null);
            pmIL.Emit(OpCodes.Stloc, x2LB);

            pmIL.Emit(OpCodes.Ldstr, "Enter the 'y' value for point 2: ");
            pmIL.EmitCall(OpCodes.Call, writeMI, null);
            pmIL.EmitCall(OpCodes.Call, readLineMI, null);
            pmIL.EmitCall(OpCodes.Call, convertInt32MI, null);
            pmIL.Emit(OpCodes.Stloc, y2LB);

            pmIL.Emit(OpCodes.Ldloc, x1LB);
            pmIL.Emit(OpCodes.Ldloc, y1LB);
            pmIL.Emit(OpCodes.Newobj, pointCtor);
            pmIL.Emit(OpCodes.Stloc, point1LB);

            pmIL.Emit(OpCodes.Ldloc, x2LB);
            pmIL.Emit(OpCodes.Ldloc, y2LB);
            pmIL.Emit(OpCodes.Newobj, pointCtor);
            pmIL.Emit(OpCodes.Stloc, point2LB);

            pmIL.Emit(OpCodes.Ldstr, "({0}, {1}) . ({2}, {3}) = {4}.");
            pmIL.Emit(OpCodes.Ldc_I4_5);
            pmIL.Emit(OpCodes.Newarr, typeof(Object));
            pmIL.Emit(OpCodes.Stloc, tempObjArrLB);

            pmIL.Emit(OpCodes.Ldloc, tempObjArrLB);
            pmIL.Emit(OpCodes.Ldc_I4_0);
            pmIL.Emit(OpCodes.Ldloc, x1LB);
            pmIL.Emit(OpCodes.Box, typeof(int));
            pmIL.Emit(OpCodes.Stelem_Ref);

            pmIL.Emit(OpCodes.Ldloc, tempObjArrLB);
            pmIL.Emit(OpCodes.Ldc_I4_1);
            pmIL.Emit(OpCodes.Ldloc, y1LB);
            pmIL.Emit(OpCodes.Box, typeof(int));
            pmIL.Emit(OpCodes.Stelem_Ref);

            pmIL.Emit(OpCodes.Ldloc, tempObjArrLB);
            pmIL.Emit(OpCodes.Ldc_I4_2);
            pmIL.Emit(OpCodes.Ldloc, x2LB);
            pmIL.Emit(OpCodes.Box, typeof(int));
            pmIL.Emit(OpCodes.Stelem_Ref);

            pmIL.Emit(OpCodes.Ldloc, tempObjArrLB);
            pmIL.Emit(OpCodes.Ldc_I4_3);
            pmIL.Emit(OpCodes.Ldloc, y2LB);
            pmIL.Emit(OpCodes.Box, typeof(int));
            pmIL.Emit(OpCodes.Stelem_Ref);

            pmIL.Emit(OpCodes.Ldloc, tempObjArrLB);
            pmIL.Emit(OpCodes.Ldc_I4_4);
            pmIL.Emit(OpCodes.Ldloc, point1LB);
            pmIL.Emit(OpCodes.Ldloc, point2LB);
            pmIL.EmitCall(OpCodes.Callvirt, pointDPBldr, null);

            pmIL.Emit(OpCodes.Box, typeof(int));
            pmIL.Emit(OpCodes.Stelem_Ref);
            pmIL.Emit(OpCodes.Ldloc, tempObjArrLB);
            pmIL.EmitCall(OpCodes.Call, writeLineMI, null);

            pmIL.Emit(OpCodes.Ret);

            Console.WriteLine("PointMain (entry point) built.");

            pointType = myTypeBldr.CreateType();

            Console.WriteLine("Type completed.");

            myAsmBldr.SetEntryPoint(pointMainBldr);

            myAsmBldr.Save(asmFileName);

            Console.WriteLine("Assembly saved as '{0}'.", asmFileName);
            Console.WriteLine("Type '{0}' at the prompt to run your new dynamically generated dot product calculator.", asmFileName);
            return pointType;
        }
    }
}